﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace SeventhHabit.Dbm.Model {
    public class ScriptRunner : IScriptRunner {
        #region Readonly & Static Fields

        private readonly IVersionedDatabase _targetDatabase;

        #endregion

        #region Fields

        private int _majorReleaseNumber;
        private int _minorReleaseNumber;
        private int _pointReleaseNumber;

        private string _scriptName = string.Empty;

        #endregion

        #region C'tors

        public ScriptRunner(IVersionedDatabase targetDatabase) {
            _targetDatabase = targetDatabase;
        }

        #endregion

        #region Instance Methods

        private void ExtractReleaseNumbers() {
            string fileName = Path.GetFileNameWithoutExtension(_scriptFilePath);
            string[] splitFileName = fileName.Split('.');

            const string message = @"filePath must be in the format nnn.nnn.nnnn.*.sql (or .nsql) where nnn[n] can be converted to an integer.";

            if (splitFileName.Length < 3)
                throw new Exception(message);

            var releaseNumbers = new int[3];
            for (int i = 0; i < 3; i++) {
                if (!int.TryParse(splitFileName[i], out releaseNumbers[i]))
                    throw new Exception(message);
            }

            _majorReleaseNumber = releaseNumbers[0];
            _minorReleaseNumber = releaseNumbers[1];
            _pointReleaseNumber = releaseNumbers[2];

            if (fileName.Length > 13) {
                _scriptName = fileName.Substring(13);
            }
        }

        private bool IsMajorRelease() {
            return _majorReleaseNumber > _targetDatabase.MajorReleaseNumber;
        }

        private bool IsMinorRealease() {
            return (_majorReleaseNumber == _targetDatabase.MajorReleaseNumber) &&
                   (_minorReleaseNumber > _targetDatabase.MinorReleaseNumber);
        }

        private bool IsPointRelease() {
            return (_majorReleaseNumber == _targetDatabase.MajorReleaseNumber) &&
                   (_minorReleaseNumber == _targetDatabase.MinorReleaseNumber) &&
                   (_pointReleaseNumber > _targetDatabase.PointReleaseNumber);
        }

        private bool IsReleaseNewer() {
            return IsMajorRelease() || IsMinorRealease() || IsPointRelease();
        }

        private IEnumerable<string> ListScriptsFromFile() {
            string scriptSequence;
            using (StreamReader reader = File.OpenText(_scriptFilePath))
                scriptSequence = reader.ReadToEnd();

            string[] scripts = scriptSequence.Split(new[] {"\r\nGO\r\n"}, StringSplitOptions.None);
            return scripts.Where(script => script.Trim().Length > 0).ToList();
        }

        private string ScriptToLogSchemaChanges() {
            const string changeSchemaLogTemplate =
                @"INSERT INTO SchemaChangeLog (MajorReleaseNumber, MinorReleaseNumber, PointReleaseNumber, ScriptName, DateApplied) VALUES ({0}, {1}, {2}, N'{3}', '{4}')";
            return string.Format(changeSchemaLogTemplate,
                                 _majorReleaseNumber,
                                 _minorReleaseNumber,
                                 _pointReleaseNumber,
                                 _scriptName,
                                 DateTime.Now.ToUniversalTime().ToString("s"));
        }

        #endregion

        #region IScriptRunner Members

        private string _scriptFilePath;

        public void Execute(string scriptFilePath) {
           _scriptFilePath = scriptFilePath;
            ExtractReleaseNumbers();
            if (!IsReleaseNewer()) return;

            var sqlScripts = new List<string>();
            sqlScripts.AddRange(ListScriptsFromFile());
            sqlScripts.Add(ScriptToLogSchemaChanges());

            bool transactional = false;
            if (Path.GetExtension(scriptFilePath).ToLower() == "sql") 
                transactional = true;

            _targetDatabase.ExecuteScripts(sqlScripts.ToArray(), transactional);
        }

        #endregion
    }
}